﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Content;
using SpaceShooter.Extensions;
using SpaceShooter.ParticleSystem;
using SpaceShooter.GameObjects;
using SpaceShooter.Components;
using System.Diagnostics;
using SpaceShooter.Common;

namespace SpaceShooter.Screens
{
    public class GameScreen : IGameScreen
    {
        private const float FarPlaneDistance = 50000.0f;

        private Type[] availableEnemies = new Type[]
        {
            typeof(P1PencilGameObject), typeof(P2PencilGameObject), typeof(P2SaucerGameObject),
            typeof(P2WedgeGameObject), typeof(UfoGameObject)
        };

        private BaseGame game;

        private GameExitController gameExitController;

        private SpriteBatch spriteBatch;
        private SpriteFont spriteFont;

        private Texture2D background;

        private Random rand = new Random();
        private float bulletRotation = 0f;

        // Object position caches
        private ChaseCamera camera;
        private OrthographicCamera headUpCamera;
        private Player player;
        private IList<GameObject> enemiesList = new List<GameObject>();

        public GameScreen(BaseGame game)
        {
            this.game = game;
        }

        public ICamera DefaultCamera 
        {
            get { return this.camera; }
        }

        public GameField GameField { get; private set; }

        public void Initialize()
        {
            this.GameField = new GameField();
            this.GameField.PlayerPosition = new Vector3(0f, -2000f, 0f);
            this.GameField.PlayerDirection = Vector3.Forward;
            this.GameField.FrontEdge = -FarPlaneDistance;
            this.GameField.BackEdge = 1000f;
            this.GameField.LeftEdge = -10000f;
            this.GameField.RightEdge = 10000f;
            this.GameField.TopEdge = 5000f;
            this.GameField.BottomEdge = -5000f;

            this.enemiesList.Foreach(x => x.Initialize());

            this.headUpCamera = new OrthographicCamera();
            this.headUpCamera.Position = Vector3.Zero;
            this.headUpCamera.LookAt = Vector3.Forward;
            this.headUpCamera.Up = Vector3.Up;
            this.headUpCamera.NearPlaneDistance = -2000f;
            this.headUpCamera.FarPlaneDistance = 2000f;
            this.headUpCamera.Width = 1440f;
            this.headUpCamera.Height = 900f;
            this.headUpCamera.UpdateMatrices();

            this.camera = new ChaseCamera();
            this.camera.AspectRatio = this.game.GraphicsDevice.Viewport.AspectRatio;
            this.camera.Damping = 400f;
            this.camera.Stiffness = 1000f;
            this.camera.FarPlaneDistance = FarPlaneDistance;
            this.camera.NearPlaneDistance = 100f;
            this.camera.DesiredPositionOffset = new Vector3(0.0f, 2000.0f, 5000.0f);
            this.camera.LookAtOffset = new Vector3(0.0f, 1500.0f, 0.0f);

            gameExitController = new GameExitController();
        }

        public void LoadContent()
        {
            this.spriteBatch = new SpriteBatch(this.game.GraphicsDevice);

            this.spriteFont = this.game.Content.Load<SpriteFont>(@"Fonts\scoreboard");

            this.background = this.game.Content.Load<Texture2D>(@"Textures\B1_stars");

            this.player = new Player(this);
            this.player.SetPosition(this.GameField.PlayerPosition);
            this.player.SetDirection(this.GameField.PlayerDirection);
            this.player.Initialize();
            this.player.SetKeystrokeTable(new KeyStrokeTable()
            {
                ChangeWeapon = new KeyBehaviour() { KeyboardKey = Keys.Tab, KeyPressType = KeyPressType.Single },
                FlyBack = new KeyBehaviour() { KeyboardKey = Keys.Down, KeyPressType = KeyPressType.Continuous },
                FlyForward = new KeyBehaviour() { KeyboardKey = Keys.Up, KeyPressType = KeyPressType.Continuous },
                FlyLeft = new KeyBehaviour() { KeyboardKey = Keys.Left, KeyPressType = KeyPressType.Continuous },
                FlyRight = new KeyBehaviour() { KeyboardKey = Keys.Right, KeyPressType = KeyPressType.Continuous },
                ShieldsUp = new KeyBehaviour() { KeyboardKey = Keys.LeftControl, KeyPressType = KeyPressType.Continuous },
                ShootWeapon = new KeyBehaviour() { KeyboardKey = Keys.Space, KeyPressType = KeyPressType.Single }
            });

            this.UpdateCamera(this.player);

            //CollisionManager.AddObject(this.player);
        }

        public void UnloadContent()
        {
            ParticleSystemManager.Clear();
            MissileManagerComponent.Clear();
        }

        public void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (gameExitController.CheckExitGame())
            {
                this.game.ExitScreen();
                return;
            }

            this.player.Update(gameTime);

            this.UpdateCamera(this.player);
            this.camera.Update(gameTime);

            this.bulletRotation += MathHelper.PiOver2 * (float)gameTime.ElapsedGameTime.TotalSeconds;
            this.bulletRotation = MathHelper.WrapAngle(this.bulletRotation);

            // enemies
            this.enemiesList.Foreach(enemy => enemy.Update(gameTime));

            // spawn enemies
            if (rand.Next(53) == 1)
            {
                this.SpawnEnemy();
            }

            this.enemiesList.Foreach(o => o.RemoveFlag = !this.GameField.IsInside(o.Position));
            this.enemiesList.RemoveMarkedObjected();

            CollisionManager.PerformCollisionDetection(this.CollisionOccured);

            this.enemiesList.RemoveMarkedObjected();
            MissileManagerComponent.GetMissileList().RemoveMarkedObjected();

            if (this.player.RemoveFlag)
            {
                this.game.ExitScreen();
            }

            ParticleSystemManager.AllParticleSystems.Foreach(p => p.SetCamera(this.camera.View, this.camera.Projection));
        }

        private void SpawnEnemy()
        {
            int enemyIndex = rand.Next(0, availableEnemies.Length);

            var enemy = (GameObject)Activator.CreateInstance(availableEnemies[enemyIndex], new object[] { this });
            enemy.SetPosition(new Vector3(
                (float)rand.NextDouble() * (this.GameField.RightEdge - this.GameField.LeftEdge) + this.GameField.LeftEdge,
                -2000,
                this.GameField.FrontEdge + 1000f));
            enemy.SetDirection(Vector3.Backward);
            enemy.SetSpeed(5000.0f);
            this.enemiesList.Add(enemy);
            CollisionManager.AddObject(enemy);
        }

        private void UpdateCamera(GameObject chaseTarget)
        {
            this.camera.ChaseDirection = chaseTarget.Direction;
            this.camera.ChasePosition = chaseTarget.Position;
            this.camera.Up = chaseTarget.Up;
        }

        private void CollisionOccured(GameObject destroyedObject)
        {
            if (destroyedObject is EnemyGameObject)
            {
                this.player.AddAmmunitionHitBonus();
                this.player.AddScore(1);
            }
        }

        public void Render(GameTime gameTime)
        {
            var renderState = this.game.GraphicsDevice.RenderState;

            RenderBackground(renderState);

            this.enemiesList.Foreach(enemy => ModelHelper.DrawGameObject(this.camera, enemy, this.EffectsSetter));

            ModelHelper.DrawGameObject(this.camera, this.player, this.EffectsSetter);
        }

        private void EffectsSetter(BasicEffect effect)
        {
            effect.FogEnabled = true;
            effect.FogColor = Vector3.Zero;
            effect.FogStart = FarPlaneDistance * 0.9f;
            effect.FogEnd = FarPlaneDistance;
        }

        private Rectangle GetWindowSize()
        {
            return new Rectangle(
                this.game.GraphicsDevice.Viewport.X,
                this.game.GraphicsDevice.Viewport.Y,
                this.game.GraphicsDevice.Viewport.Width,
                this.game.GraphicsDevice.Viewport.Height);
        }

        /// <summary>
        /// Helper for drawing our message text.
        /// </summary>
        void RenderBackground(RenderState renderState)
        {
            this.spriteBatch.Begin();

            this.spriteBatch.Draw(this.background, this.GetWindowSize(), Color.White);
 
            this.RenderPlayerData();
            this.RenderHeadUpDisplay();
            
            this.spriteBatch.End();

            renderState.DepthBufferEnable = true;
            renderState.AlphaBlendEnable = false;
            renderState.AlphaTestEnable = false;

            var rocketModel = this.game.Content.Load<Model>(@"Models/p1_rocket");
            float rocketSize = rocketModel.Meshes.Max(m => m.BoundingSphere.Radius);

            Matrix rocketPos =
                Matrix.CreateScale(1f / rocketSize * 50f) *
                Matrix.CreateRotationY(bulletRotation) *
                Matrix.CreateTranslation(new Vector3(-70f, 90f, 0f)) *
                Matrix.Identity;

            ModelHelper.DrawGameObject(this.headUpCamera, rocketModel, rocketPos, s => { });
        }

        private void RenderPlayerData()
        {
            string score = string.Format("{0:00000}", this.player.Score);
            string ammunition = string.Format("{0:00000}", this.player.Ammunition);

            this.spriteBatch.DrawString(this.spriteFont, score, new Vector2(100, 10), Color.White);
            this.spriteBatch.DrawString(this.spriteFont, ammunition, new Vector2(100, 50), DetermineAmmunitionColor(this.player.Ammunition));
        }

        private void RenderHeadUpDisplay()
        {
        }

        private Color DetermineAmmunitionColor(int remainingAmmunition)
        {
            if (remainingAmmunition < 50)
                return Color.Red;
            else
                return Color.White;
        }

        public Game Game
        {
            get { return this.game; }
        }
    }
}
